/* Sentinel - IRC Statistical and Operator Services
** listserv.c - List service functionality
**
** Copyright W. Campbell and others.  See README for more details
** Some code Copyright: Jonathan George, Kai Seidler, ircd-hybrid Team,
**                      IRCnet IRCD developers.
**
** $Id: listserv.c 7 2010-02-01 19:17:42Z dubkat $
**
** The idea for this code is based heavily on IRCnet's ALIS service, by
** Thomas *Engerim* Kuiper and Michal *pht* Svoboda.  This implementation
** attempts to maintain a large degree of compatability between the accepted
** commands and the output to the ALIS service.
*/

#include "stats.h"
#include "struct.h"
#include "proto.h"
#include "s_string.h"
#include "slog.h"
#include "mem.h"
#include "support.h"
#include "match.h"

#ifdef LISTSERV

#define LMODE_NOCHECK	0
#define LMODE_SET	1
#define LMODE_UNSET	2
#define LMODE_EQUAL	3

static char errstr[BUFSIZE];
static char *ls_safe_mode(struct Channel *);
static unsigned int ls_parse_cmode(char *);

static int l_help(struct User *, int, char **);
static int l_cping(struct User *, int, char **);
static int l_cversion(struct User *, int, char **);
static int l_list(struct User *, int, char **);
static int l_status(struct User *, int, char **);

static struct MsgTable list_table[] =
  {
    { "HELP", 0, 0, NULL, l_help },
    { "LIST", 0, 0, NULL, l_list },
    { "STATUS", 0, 0, NULL, l_status },
    { "\001PING", 0, 0, NULL, l_cping },
    { "\001VERSION\001", 0, 0, NULL, l_cversion },
    { (char *)0, 0, 0, NULL, (int (*)()) 0 }
  };

static struct HelpTable help_table[] =
  {
    { "LIST", "help/listserv/list", 0 },
    { "STATUS", "help/listserv/status", 0 },
    { (char *)0, (char *)0, 0 }
  };

struct CMode
{
  char mode;
  unsigned int value;
};

static struct CMode mode_list[] =
  {
    { 'i', CMODE_INVITE },
    { 'm', CMODE_MOD },
    { 'n', CMODE_NOEXT },
    { 't', CMODE_TOPIC },
    { 'O', CMODE_OPERONLY },
    { 'r', CMODE_REGISTERED },
    { 'R', CMODE_REGONLY },
    { 'c', CMODE_NOCOLOUR },
    { '\0', 0L }
  };

struct ListStats
{
  int total;
  int max;
  int min;
  int topic;
  int showm;
  int showt;
  int skip;
  int mode;
  int active;
  int error;
};

static struct ListStats liststats;

/* List which modes we accept, so for = mode matching, we can ignore any
** modes we don't care about
*/
#define LS_MODEMASK (CMODE_INVITE|CMODE_MOD|CMODE_NOEXT|CMODE_TOPIC| \
                     CMODE_OPERONLY|CMODE_REGISTERED|CMODE_REGONLY| \
                     CMODE_NOCOLOUR)

void listserv(struct User *u, char *param)
{
  char *parv[MAXPARA];
  int parc = 0;
  int i;
  int status;
  static char buf[BUFSIZE];

  /* Clear the parv */
  for (i = 0; i < MAXPARA; i++)
    parv[i] = NULL;

  /* Clear the error string */
  errstr[0] = '\0';

  strlcpy(buf, param, BUFSIZE);

  if (param != NULL)
    parc = msgtoken(param, parv);

  netinfo.requests++;

  if (!parc)
    return ;

  if (parv[0][0] != '\001')
    slog(DEFAULT_LOG, L_INFO, "%s received message from %s: %s", ListServ.nick,
         u->nick, buf);

  /* All the preliminary junk is done, now we have to go through
  ** the tables and call the handler for that command
  */
  for (i = 0; list_table[i].handler; i++)
  {
    if (!strcasecmp(parv[0], list_table[i].name))
    {
      status = list_table[i].handler(u, parc, parv);
      if (status == 1)
        slog(DEFAULT_LOG, L_ERR, "Error with ListServ command %s: %s",
             parv[0], (errstr[0]) ? errstr : "No error message");
      /* Matched a value, done, then return */
      return ;
    }
  }
  /* If we get here, we did not get a valid command...
  ** Make sure not to say anything to the user if it was a 
  ** CTCP that was not matched
  */
  if (*parv[0] == '\001')
    return ;
  notice(u->nick, ListServ.nick, "Invalid command: %s", parv[0]);
  return ;
}

/* In all of these functions, parv[0] contains the command, followed
** by the parameters
*/

static int l_help(struct User *u, int parc, char *parv[])
{
  int i;

  spy("%s:  HELP Request by %s for %s", ListServ.nick, u->nick,
      (parv[1]) ? parv[1] : "NO QUERY");
  sendto_dcc_flag(CF_COMMANDS, "%s:  HELP Request by %s for %s",
                  ListServ.nick, u->nick, (parv[1]) ? parv[1] : "NO QUERY");
  if (parc < 2)
  {
    send_help_file(u->nick, ListServ.nick, "help/listserv/index", "index");
    if (u->myuser != NULL)
      send_help_file(u->nick, ListServ.nick, "help/listserv/aindex", "aindex");
    return 0;
  }

  for (i = 0; help_table[i].filename; i++)
  {
    if (!strcasecmp(parv[1], help_table[i].name))
    {
      if (help_table[i].myuonly)
      {
        if (u->myuser)
          send_help_file(u->nick, ListServ.nick, help_table[i].filename,
                         help_table[i].name);
        else
          notice(u->nick, ListServ.nick, "Permission Denied.");
      }
      else
      {
        send_help_file(u->nick, ListServ.nick, help_table[i].filename,
                       help_table[i].name);
      }

      return 0;
    }
  }

  notice(u->nick, ListServ.nick, "Unknown Help Topic: \2%s\2", parv[1]);
  snprintf(errstr, BUFSIZE, "Unknown HELP topic: %s", parv[1]);
  return 1;
}

static int l_cping(struct User *u, int parc, char *parv[])
{
  char command[BUFSIZE];

  ExpandParv(command, BUFSIZE, 1, parc, parv);
  if (command[0] == '\0')
    return 1;
  sts(":%s NOTICE %s :\001PING %s", ListServ.nick, u->nick, command);
  slog(DEFAULT_LOG, L_INFO, "CTCP PING from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int l_cversion(struct User *u, int parc, char *parv[])
{
  sts(":%s NOTICE %s :\001VERSION %s %s -> %s\001",
      ListServ.nick, u->nick, stats_version, VersionFlags(), stats_date);
  slog(DEFAULT_LOG, L_INFO, "CTCP VERSION from %s!%s@%s", u->nick, u->username,
       u->hostname);

  return 0;
}

static int l_list(struct User *u, int parc, char *parv[])
{
  int qmin = 0, qmax = 0;
  char *qtopic = NULL, *qchannel = NULL;
  int qskip = 0, qshowmode = 0, qshowtopic = 0;
  int qactive = 0, qmodetype = LMODE_NOCHECK;
  unsigned int qmodematch = 0;
  int count, shown;
  int i;
  char summary[BUFSIZE];
  char displine[BUFSIZE];
  char tmpbuf[BUFSIZE];
  struct Channel *c;

  if (parc >= 2)
    qchannel = parv[1];
  else
  {
    liststats.error++;
    notice(u->nick, ListServ.nick, "Invalid parameters, please "
            "consult /msg %s help list", ListServ.nick);
    return 0;
  }

  for (i = 2; i < parc; i++)
  {
    if (strcasecmp(parv[i], "-min") == 0)
    {
      i++; /* Skip to the -min parameter */
      if (i >= parc || parv[i][0] == '-')
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -min parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      qmin = atoi(parv[i]);
      liststats.min++;
    }
    else if (strcasecmp(parv[i], "-max") == 0)
    {
      i++; /* Skip to the -max parameter */
      if (i >= parc || parv[i][0] == '-')
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -max parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      qmax = atoi(parv[i]);
      liststats.max++;
    }
    else if ((strcasecmp(parv[i], "-topic") == 0) ||
        (strcasecmp(parv[i], "-t") == 0))
    {
      i++; /* Skip to the -topic parameter */
      if (i >= parc || parv[i][0] == '-')
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -topic parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      qtopic = parv[i];
      liststats.topic++;
    }
    else if ((strcasecmp(parv[i], "-show") == 0) ||
        (strcasecmp(parv[i], "-s") == 0))
    {
      char *c;

      i++; /* Skip to the -show parameter */
      if (i >= parc || parv[i][0] == '-')
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -show parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      for (c = parv[i]; *c != '\0'; c++)
      {
        switch(*c)
        {
          case 'm':  qshowmode = 1; liststats.showm++; break;
          case 't':  qshowtopic = 1; liststats.showt++; break;
          default:
            liststats.error++;
            notice(u->nick, ListServ.nick,
                "Invalid -show parameter %c, please consult /msg %s help list",
                *c, ListServ.nick);
            return 0;
        }
      }
    }
    else if (strcasecmp(parv[i], "-skip") == 0)
    {
      i++;
      if (i >= parc || parv[i][0] == '-')
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -skip parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      qskip = atoi(parv[i]);
      liststats.skip++;
    }
    else if ((strcasecmp(parv[i], "-active") == 0) ||
             (strcasecmp(parv[i], "-a") == 0))
    {
      i++;
      if (i >= parc || parv[i][0] == '-')
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -active parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      qactive = atoi(parv[i]);
      liststats.active++;
    }
    else if ((strcasecmp(parv[i], "-mode") == 0) ||
             (strcasecmp(parv[i], "-m") == 0))
    {
      i++;
      if (i >= parc)
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -mode parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      if (parv[i][0] == '+')
        qmodetype = LMODE_SET;
      else if (parv[i][0] == '-')
        qmodetype = LMODE_UNSET;
      else if (parv[i][0] == '=')
        qmodetype = LMODE_EQUAL;
      else
      {
        liststats.error++;
        notice(u->nick, ListServ.nick,
                "Invalid -mode parameters, please consult /msg %s help list",
                ListServ.nick);
        return 0;
      }
      liststats.error++;
      qmodematch = ls_parse_cmode(&parv[i][1]);
    }
    /* else, it's an invalid command */
    else
    {
      liststats.error++;
      notice(u->nick, ListServ.nick,
              "Invalid option %s, please consult /msg %s help list",
              parv[i], ListServ.nick);
      return 0;
    }
  }

  liststats.total++;
  count = shown = 0;

  snprintf(summary, BUFSIZE,
           "Query summary: searching for channels matching \"%s\"",
           qchannel);
  if (qmin > 0)
  {
    snprintf(tmpbuf, BUFSIZE, ", min %d users", qmin);
    strlcat(summary, tmpbuf, BUFSIZE);
  }
  if (qmax > 0)
  {
    snprintf(tmpbuf, BUFSIZE, ", max %d users", qmax);
    strlcat(summary, tmpbuf, BUFSIZE);
  }
  if (qtopic != NULL)
  {
    snprintf(tmpbuf, BUFSIZE, ", topic containing \"%s\"", qtopic);
    strlcat(summary, tmpbuf, BUFSIZE);
  }
  if (qmodetype != LMODE_NOCHECK)
  {
    strlcat(summary, ", matching mode", BUFSIZE);
  }
  if (qshowmode)
  {
    strlcat(summary, ", showing mode", BUFSIZE);
  }
  if (qshowtopic)
  {
    strlcat(summary, ", showing who set topic", BUFSIZE);
  }
  if (qactive)
  {
    snprintf(tmpbuf, BUFSIZE, ", active within the last %d seconds",
             qactive);
    strlcat(summary, tmpbuf, BUFSIZE);
  }
  notice(u->nick, ListServ.nick, "%s", summary);

  notice(u->nick, ListServ.nick, "Returning a maximum of %i channel names.",
         ListServ.default_max);
  if (qskip > 0)
    notice(u->nick, ListServ.nick, "Skipping the first %i.", qskip);

  for (i = 0; i < C_TABLE_SIZE; i++)
  {
    for (c = chanlist[i]; c != NULL; c = c->next)
    {
      if ((c->modes & CMODE_PRIV) || (c->modes & CMODE_SEC))
        continue;
      if (match(qchannel, c->name) == 0)
      {
        if ((qmax > 0) && (c->nummembers > qmax))
          continue;
        if ((qmin > 0) && (c->nummembers < qmin))
          continue;
        /* If we want channels with activity in the last qactive seconds,
        ** drop any that have been idle for longer
        */
        if ((qactive > 0) && ((CurrentTime - c->last) > qactive))
          continue;

        /* mode matching... */
        if (qmodetype != LMODE_NOCHECK)
        {
          if (qmodetype == LMODE_SET)
          {
            if (!(c->modes & qmodematch)) /* Skip if it's NOT set */
              continue;
          }
          else if (qmodetype == LMODE_UNSET)
          {
            if (c->modes & qmodematch) /* Skip, any given modes ARE set */
              continue;
          }
          else if (qmodetype == LMODE_EQUAL)
          {
            if (!((c->modes & LS_MODEMASK) == qmodematch))
              continue; /* Skip if we don't have exactly the modes asked for */
          }
        }

        if (qtopic != NULL)
        {
          if (c->topic == NULL)
            continue; /* No topic set means we can't match */
          if (match(qtopic, c->topic) != 0)
            continue;
        }

        count++;
        if ((qskip > 0) && (count <= qskip))
          continue;
        /* Inefficient */
        if ((ListServ.default_max > 0) && (shown >= ListServ.default_max))
          continue;

        shown++;
        snprintf(displine, BUFSIZE, "%3d) %-50s", shown, c->name);
        if (qshowmode)
        {
          snprintf(tmpbuf, BUFSIZE, " [%s]", ls_safe_mode(c));
          strlcat(displine, tmpbuf, BUFSIZE);
        }
        snprintf(tmpbuf, BUFSIZE, " \002%3d\002 %s", c->nummembers,
                 (c->topic) ? c->topic : "No topic is set.");
        strlcat(displine, tmpbuf, BUFSIZE);
        if (qshowtopic)
        {
          if (c->topicauthor != NULL)
          {
            snprintf(tmpbuf, BUFSIZE, " (%s [%s])", c->topicauthor,
                     sctime(c->topicts));
            strlcat(displine, tmpbuf, BUFSIZE);
          }
        }
        notice(u->nick, ListServ.nick, "%s", displine);
      }
    }
  }

  notice(u->nick, ListServ.nick, "Found %d visible channels.", shown);
  if ((count - qskip - ListServ.default_max) > 0)
    notice(u->nick, ListServ.nick,
            "There are %i more. Use the \"-skip %i\" option with your query to "
            "display them.", (count - qskip - ListServ.default_max),
            (qskip + ListServ.default_max));

  return 0;
}

static char *ls_safe_mode(struct Channel *c)
{
  static char modebuf[LINE];
  char *ptr = modebuf;
  int i;

  *ptr = '+';
  ptr++;

  for (i = 0; mode_list[i].mode != '\0'; i++)
  {
    if (c->modes & mode_list[i].value)
    {
      *ptr = mode_list[i].mode;
      ptr++;
    }
  }

  *ptr = '\0';
  return modebuf;
}

static unsigned int ls_parse_cmode(char *mode)
{
  unsigned int retval = 0;
  char *ptr = mode;
  int i;

  /* Already picked off the +, -, or = */
  if (*ptr == '\0')
    return 0; /* No modes at all */

  for (; *ptr != '\0'; ptr++)
  {
    for (i = 0; mode_list[i].mode != '\0'; i++)
    {
      if (*ptr == mode_list[i].mode)
      {
        retval |= mode_list[i].value;
        break;
      }
    }
  }

  return retval;
}

static int l_status(struct User *u, int parc, char *parv[])
{
  notice(u->nick, ListServ.nick, "In %s,", timediff(CurrentTime - me.t_start));
  notice(u->nick, ListServ.nick,
          "There have been %d successful queries and %d with errors.",
          liststats.total, liststats.error);
  notice(u->nick, ListServ.nick,
          "Of the successful queries, %d used -min, %d used -max,",
          liststats.min, liststats.max);
  notice(u->nick, ListServ.nick,
          "%d used -topic, %d used -show m, %d used -show t,",
          liststats.topic, liststats.showm, liststats.showt);
  notice(u->nick, ListServ.nick,
          "%d used -skip, %d used -mode, and %d used -active.",
          liststats.skip, liststats.mode, liststats.active);

  return 0;
}

#endif
